From: Sebastian Wick Date: Sat, 21 Jun 2025 22:16:56 +0000 (+0200) Subject: brightnessManager: Handle monitor backlight brightness X-Git-Tag: archive/raspbian/50.5-1+rpi1~1^2~116^2^2~7 X-Git-Url: https://dgit.raspbian.org/%22http://www.example.com/cgi/%22/%22http:/www.example.com/cgi/%22?a=commitdiff_plain;h=d910f90ac7fe0927467624103ddffa40bc3a8bf6;p=gnome-shell.git brightnessManager: Handle monitor backlight brightness This implements a backlight manager which creates a global and multiple per monitor BrightnessScales and updates the backlight based on the BrightnessScales changing and the backlight changing. It shows the OSD in response to changes. In the future it will also handle dimming, auto brightness and keyboard shortcuts. The idea is that the per monitor scale changes the ratio between monitor brighnesses and the global scale scales the monitor brightnesses accordingly. Part-of: --- diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml index c116810a7..dc182cc74 100644 --- a/js/js-resources.gresource.xml +++ b/js/js-resources.gresource.xml @@ -16,6 +16,7 @@ misc/animationUtils.js misc/breakManager.js + misc/brightnessManager.js misc/config.js misc/dateUtils.js misc/dbusErrors.js diff --git a/js/misc/brightnessManager.js b/js/misc/brightnessManager.js new file mode 100644 index 000000000..2e7cc5b2b --- /dev/null +++ b/js/misc/brightnessManager.js @@ -0,0 +1,253 @@ +import Gio from 'gi://Gio'; +import GObject from 'gi://GObject'; + +import * as Main from '../ui/main.js'; + +const SCALE_VALUE_CHANGE_EPSILON = 0.001; + +export const BrightnessManager = GObject.registerClass({ + Signals: { + 'changed': {}, + }, +}, class BrightnessManager extends GObject.Object { + constructor() { + super(); + + this._globalScale = null; + this._monitorScales = new Map(); + + const monitorManager = global.backend.get_monitor_manager(); + monitorManager.connectObject('monitors-changed', + this._monitorsChanged.bind(this), this); + this._monitorsChanged(); + } + + get globalScale() { + return this._globalScale; + } + + get scales() { + return [...this._monitorScales.values()]; + } + + _monitorsChanged() { + const monitors = global.backend + .get_monitor_manager() + .get_logical_monitors() + .filter(lm => { + return lm.get_monitors() + .some(m => m.get_backlight() && m.is_active()); + }); + + if (monitors.length === 0) { + this._globalScale = null; + } else if (!this._globalScale) { + this._globalScale = new BrightnessScale(_('Brightness'), 1.0); + this._globalScale.connect('notify::value', () => { + if (this._inhibitUpdates) + return; + this._globalScaleChanged = true; + this._sync(); + }); + } + + this._monitorScales.clear(); + + for (const monitor of monitors) { + const scale = new MonitorBrightnessScale(monitor, 1.0); + scale._scaleChanged = true; + + scale.connectObject( + 'backlights-changed', () => this._sync(), + 'notify::value', () => { + if (this._inhibitUpdates) + return; + scale._scaleChanged = true; + this._sync(); + }, this); + + this._monitorScales.set(monitor, scale); + } + + if (this._globalScale) + this._sync({showOSD: false}); + + this.emit('changed'); + } + + _sync({showOSD = true} = {}) { + if (this._inhibitUpdates) + return; + this._inhibitUpdates = true; + + // Handle changed backlights + for (const scale of this._monitorScales.values()) + scale.syncWithBacklight(); + + // Find scales which have been changed (and reset _scaleChanged) + const changedScales = [...this._monitorScales.values()].filter(s => { + const c = s._scaleChanged; + s._scaleChanged = false; + return c; + }); + + if (changedScales.length > 0) { + // update the factors of all the scales when a scale changes + + // normalize everything to the maximum of all scales + const max = Math.max(...[...this._monitorScales.values()] + .map(s => s.value)); + + // if max is 0 we can't deduce any ratios, so don't try + if (max > 0.01) { + for (const scale of this._monitorScales.values()) + scale.updateScaleFactor(max); + } + + // the global scale always follows the maximum, because one monitor + // scale is at the maximum and we want the global scale to be a + // factor we apply on the ratio of the monitor scales. + this._globalScale.value = max; + + if (showOSD) + this._showOSD(changedScales); + } else if (this._globalScaleChanged) { + // if the global scale changed, update the monitor scales according + // to their scaleFactor and the global scale. + + this._globalScaleChanged = false; + + for (const scale of this._monitorScales.values()) + scale.syncWithScale(this._globalScale); + + if (showOSD) + this._showOSD(this._monitorScales.values()); + } + + // Update the actual backlight according to the new monitor brightnesses + for (const scale of this._monitorScales.values()) + scale.setBacklight(scale.value); + + this._inhibitUpdates = false; + } + + _showOSD(monitorScales) { + const osdMonitors = {}; + for (const scale of monitorScales) { + const level = scale.value; + osdMonitors[scale.monitor.get_number()] = {level}; + } + + Main.osdWindowManager.show( + Gio.Icon.new_for_string('display-brightness-symbolic'), + null, + osdMonitors + ); + } +}); + +export const BrightnessScale = GObject.registerClass({ + Properties: { + 'value': GObject.ParamSpec.float( + 'value', null, null, + GObject.ParamFlags.READWRITE, + 0, 1.0, 1.0), + }, +}, class BrightnessScale extends GObject.Object { + constructor(name, value) { + super({ + value, + }); + + this._name = name; + } + + get name() { + return this._name; + } + + get value() { + return this._value; + } + + set value(value) { + this._setValue(value); + } + + _setValue(value) { + value = Math.clamp(value, 0.0, 1.0); + if (Math.abs(value - this._value) < SCALE_VALUE_CHANGE_EPSILON) + return; + this._value = value; + this.notify('value'); + } +}); + +const MonitorBrightnessScale = GObject.registerClass({ + Signals: { + 'backlights-changed': {}, + }, +}, class MonitorBrightnessScale extends BrightnessScale { + constructor(monitor, value) { + const name = monitor.get_monitors()[0].get_display_name(); + + super(name, value); + + this._monitor = monitor; + this._currentBacklightBrightness = -1; + this._scaleFactor = 1.0; + + for (const backlight of this._getBacklights()) { + backlight.connectObject('notify::brightness', () => { + this.emit('backlights-changed'); + }, this); + } + } + + get monitor() { + return this._monitor; + } + + _getBacklights() { + return this._monitor.get_monitors() + .filter(m => m.get_backlight() && m.is_active()) + .map(m => m.get_backlight()); + } + + _getRelativeBrightness(backlight) { + const {brightness, brightnessMin: min, brightnessMax: max} = backlight; + return (brightness - min) / (max - min); + } + + _setRelativeBrightness(backlight, brightness) { + const {brightnessMin: min, brightnessMax: max} = backlight; + backlight.brightness = min + ((max - min) * brightness); + } + + syncWithBacklight() { + const [backlight] = this._getBacklights(); + + if (backlight.brightness === this._currentBacklightBrightness) + return false; + this._currentBacklightBrightness = backlight.brightness; + + this.value = this._getRelativeBrightness(backlight); + return true; + } + + syncWithScale(globalScale) { + this.value = globalScale.value * this._scaleFactor; + } + + setBacklight(brightness) { + const backlights = this._getBacklights(); + for (const backlight of backlights) + this._setRelativeBrightness(backlight, brightness); + + this._currentBacklightBrightness = backlights[0].brightness; + } + + updateScaleFactor(max) { + this._scaleFactor = this.value / max; + } +}); diff --git a/js/ui/main.js b/js/ui/main.js index 76b917f48..cd2417a6e 100644 --- a/js/ui/main.js +++ b/js/ui/main.js @@ -9,6 +9,7 @@ import St from 'gi://St'; import * as AccessDialog from './accessDialog.js'; import * as AudioDeviceSelection from './audioDeviceSelection.js'; import * as BreakManager from '../misc/breakManager.js'; +import * as BrightnessManager from '../misc/brightnessManager.js'; import * as Config from '../misc/config.js'; import * as Components from './components.js'; import * as CtrlAltTab from './ctrlAltTab.js'; @@ -96,6 +97,7 @@ export let screenTimeDBus = null; export let breakManagerDispatcher = null; export let timeLimitsManager = null; export let timeLimitsDispatcher = null; +export let brightnessManager = null; let _startDate; let _defaultCssStylesheet = null; @@ -258,6 +260,8 @@ async function _initializeUI() { breakManagerDispatcher = new BreakManager.BreakDispatcher(breakManager); timeLimitsDispatcher = new TimeLimitsManager.TimeLimitsDispatcher(timeLimitsManager); + brightnessManager = new BrightnessManager.BrightnessManager(); + global.connect('shutdown', () => { // Block shutdown until the session history file has been written const loop = new GLib.MainLoop(null, false); diff --git a/po/POTFILES.in b/po/POTFILES.in index b3edf7624..ee0829c96 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -14,6 +14,7 @@ js/gdm/authPrompt.js js/gdm/loginDialog.js js/gdm/util.js js/misc/breakManager.js +js/misc/brightnessManager.js js/misc/systemActions.js js/misc/timeLimitsManager.js js/misc/util.js